// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Program.cs" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace ConsoleHost
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Threading;
    using System.Xml;
    using System.Xml.XPath;
    using System.Xml.Xsl;
    using CmdLine;
    using Microsoft.Robotics.IO;
    using Microsoft.Robotics.PerformanceCounters;
    using Microsoft.Robotics.Runtime;

    /// <summary>
    /// Contains the entry point for the executable
    /// </summary>
    public class Program
    {
        /// <summary>
        /// Apply transform and token substitutions to the manifest.
        /// </summary>
        /// <param name="template">Pathname to an XSLT to be applied.</param>
        /// <param name="substitute">Set of substitutions to be made during transformation.</param>
        /// <param name="manifest">Pathname to our manifest file.</param>
        /// <returns>Transformed manifest.</returns>
        private static Manifest Transform(string template, string substitute, string manifest)
        {
            XslCompiledTransform transform = new XslCompiledTransform();
            transform.Load(template);
            using (MemoryStream ms = new MemoryStream())
            {
                // Convert comma-separated list of pairs of name=value to
                // XML fragment suitable as a parameter to the XSLT.
                // Schema is <replacement>...</replacement> containing
                // collection of <token name="..." value="..." />
                XmlDocument doc = new XmlDocument();
                XmlElement replacement = doc.CreateElement("replacement");
                if (!string.IsNullOrEmpty(substitute))
                {
                    foreach (string pair in substitute.Split(','))
                    {
                        string[] nameValue = pair.Split('=');
                        if (nameValue.Length != 2)
                        {
                            throw new ArgumentException("Expected substitution pairs in the form foo=bar,baz=goo.");
                        }

                        XmlElement token = doc.CreateElement("token");
                        token.SetAttribute("name", nameValue[0]);
                        token.SetAttribute("value", nameValue[1]);
                        replacement.AppendChild(token);
                    }

                    doc.AppendChild(replacement);
                }

                XsltArgumentList xsltArgs = new XsltArgumentList();
                xsltArgs.AddParam("substitutions", string.Empty, doc);

                transform.Transform(manifest, xsltArgs, ms);
                ms.Position = 0;
                return Serializer.Load<Manifest>(ms, Serializer.LoadKnownTypesForFile(manifest));
            }
        }

        /// <summary>
        /// The entry point for this executable. 
        /// Parses the cmdline to extract the manifest and the agents to activate.
        /// Loads the agent manifest and activates the specified agents.
        /// </summary>
        /// <param name="args">The argument list, parsed using the ConsoleHostArguments class.</param>
        private static void Main(string[] args)
        {
            ConsoleHostArguments arguments = null;
            IForwarder<SimpleAgentMessage<bool>> showUIControlPort = null;

            try
            {
                arguments = CommandLine.Parse<ConsoleHostArguments>();
            }
            catch (CommandLineException exception)
            {
                Console.WriteLine(exception.ArgumentHelp.Message);
                Console.WriteLine(exception.ArgumentHelp.GetHelpText(Console.BufferWidth));
                return;
            }

            if (arguments.InstallPerformanceCounters)
            {
                Console.WriteLine("Installing performance counters.");
                PerformanceCounterManager.SetupCounters(true);
            }

            if (arguments.Manifest == null)
            {
                return;
            }

            Console.WriteLine("Starting manifest {0} ", arguments.Manifest);
            Manifest manifest;

            if (arguments.Substitute == null)
            {
                arguments.Substitute = string.Empty;
            }           

            if (!arguments.Substitute.ToLower().Contains(ConsoleHostArguments.RobotConfigFolderString))
            {
                string robotConfigFolderParam = string.Format("{0}={1}", ConsoleHostArguments.RobotConfigFolderString, Path.Combine(ConsoleHostArguments.RobotConfigBaseFolder, Environment.MachineName));
                arguments.Substitute = AddParam(arguments.Substitute, robotConfigFolderParam);
            }

            if (string.IsNullOrEmpty(arguments.Transform))
            {
                manifest = Serializer.Load<Manifest>(arguments.Manifest);
            }
            else
            {
                manifest = Transform(arguments.Transform, arguments.Substitute, arguments.Manifest);
            }

            foreach (string agentName in arguments.Agents)
            {
                manifest.ActiveAgents.Add(agentName);
            }

            AgentLocator locator = new AgentLocator(arguments.OfflineMode);
            locator.LoadFrom(manifest);

            Console.WriteLine("Started.");
            Console.WriteLine("Press 'v' to activate the visualization agent.");
            Console.WriteLine("Press 'p' to activate the performance monitor.");
            Console.WriteLine("Press <enter> to exit :)");

            while (true)
            {
                if (!Console.IsInputRedirected && Console.KeyAvailable)
                {
                    ConsoleKeyInfo key = Console.ReadKey(true);
                    if (key.Key == ConsoleKey.V)
                    {
                        // try to bring up the visualization
                        if (showUIControlPort == null)
                        {
                            IAgentHost userInterface;
                            if (locator.TryGetAgent(arguments.Visualization, out userInterface))
                            {
                                showUIControlPort = userInterface.GetControlPort<SimpleAgentMessage<bool>>();
                            }
                            else
                            {
                                Console.WriteLine("Visualization agent is not present in the loaded manifest");
                            }
                        }

                        if (showUIControlPort != null)
                        {
                            showUIControlPort.Post(new SimpleAgentMessage<bool>(true, 0));
                        }
                    }
                    else if (key.Key == ConsoleKey.P)
                    {
                        System.Diagnostics.Process.Start("Scripts\\MicrosoftRoboticsCounters.msc");
                    }
                    else if (key.Key == ConsoleKey.Enter)
                    {
                        break;
                    }
                }
                else
                {
                    Thread.Sleep(100);
                }
            }

            // shutdown
            Console.WriteLine("Stopping.");
            locator.Deactivate();
            Console.WriteLine("Stopped.");
        }

        /// <summary>
        /// Adds the param to the current parameters list.
        /// </summary>
        /// <param name="currentParams">Current parameters</param>
        /// <param name="paramToAdd">Parameter to add to the list</param>
        /// <returns>Updated parameter list</returns>
        private static string AddParam(string currentParams, string paramToAdd)
        {
            string updatedParams;
            if (string.IsNullOrEmpty(currentParams))
            {
                updatedParams = paramToAdd;
            }
            else
            {
                updatedParams = string.Concat(currentParams, ",", paramToAdd);
            }

            return updatedParams;
        }
    }
}
